package org.baicaixiaozhan.io;

import com.google.common.collect.Multimap;
import com.google.common.collect.MultimapBuilder;
import com.google.common.io.ByteSource;
import com.opencsv.CSVWriter;
import com.opencsv.CSVWriterBuilder;
import com.opencsv.ICSVWriter;
import org.baicaixiaozhan.crypto.AES256GCM;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;

import static java.util.stream.Collectors.toList;

/**
 * DESC:
 *
 * @author : baicaixiaozhan
 * @since : 2020-12-21
 */
public class CryptoCsvFileFeatures
        implements AbstractCryptoFileFeatures<List<Map<String, Object>>, Multimap<String, List<Object>>> {

    /**
     * get file`s byte arrays
     *
     * @param file file that will be read
     * @return byte arrays
     */
    @Override
    public byte[] readFile(File file) {
        byte[] fileData = new byte[(int) file.length()];
        try (FileInputStream fileInputStream = new FileInputStream(file)) {
            fileInputStream.read(fileData);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return fileData;
    }

    /**
     * write file data to filePath
     *
     * @param filePath file output path
     * @param data     file data
     */
    @Override
    public void writeFile(String filePath, byte[] data) {
        try (FileOutputStream fileOutputStream = new FileOutputStream(filePath)) {
            fileOutputStream.write(data);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * encryption data to file
     *
     * @param data     data that will be encryption
     * @param filePath encryption file`s output path
     */
    @Override
    public void encryptDataToFile(List<Map<String, Object>> data, String filePath) {
        try {
            Writer writer = Files.newBufferedWriter(Paths.get(filePath));
            ICSVWriter csvWriter = new CSVWriterBuilder(writer)
                    .withSeparator(CSVWriter.DEFAULT_SEPARATOR)
                    .withLineEnd(CSVWriter.DEFAULT_LINE_END)
                    .build();
            // write header record
            String[] title = data.get(0).keySet().toArray(new String[0]);
            for (int i = 0; i < title.length; i++) {
                byte[] bytes = AES256GCM.encryptData(AES256GCM.KEY, title[i].getBytes(StandardCharsets.UTF_8));
                title[i] = Base64.getEncoder().encodeToString(bytes);
            }
            csvWriter.writeNext(title);

            for (Map<String, Object> map : data) {
                String[] values = map.values().stream().map(String::valueOf).toArray(String[]::new);
                csvWriter.writeNext(values);
            }
            // close the writer
            writer.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * decryption file to R data
     *
     * @param filePath that will be decryption file path
     * @return R
     */
    @Override
    public Multimap<String, List<Object>> decryptFileToObject(String filePath) {
        Multimap<String, List<Object>> multimap = MultimapBuilder.hashKeys().arrayListValues().build();

        try {
            byte[] fileBytes = readFile(new File(filePath));
            byte[] resultBytes = AES256GCM.decryptData(AES256GCM.KEY, fileBytes);
            InputStream targetStream = ByteSource.wrap(resultBytes).openStream();
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(targetStream));
            Object[] objects = bufferedReader.lines().toArray();

            multimap.put("title", Arrays.stream(objects).limit(1).collect(toList()));
            Arrays.stream(objects)
                    .skip(1)
                    .map(obj ->
                            new ArrayList<Object>(Arrays.asList(obj.toString().split(","))))
                    .forEach(obj -> multimap.put("values", obj));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return multimap;
    }
}
